package com.rizzo.infrastructure.quota.redis;

import com.rizzo.infrastructure.domain.KeyType;
import com.rizzo.infrastructure.quota.QuotaException;
import com.rizzo.infrastructure.quota.QuotaMonitor;
import com.rizzo.infrastructure.redis.key.RedisKey;
import com.rizzo.infrastructure.redis.key.RedisKeyBuilder;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.core.context.SecurityContextHolderStrategy;

import java.util.concurrent.TimeUnit;

public class RedisQuotaMonitor implements QuotaMonitor {

    private String application;
    private RedisTemplate<RedisKey, Long> redisTemplate;
    private SecurityContextHolderStrategy securityContextHolderStrategy;

    public RedisQuotaMonitor(String application, RedisTemplate<RedisKey, Long> redisTemplate,
                             SecurityContextHolderStrategy securityContextHolderStrategy) {
        this.application = application;
        this.redisTemplate = redisTemplate;
        this.securityContextHolderStrategy = securityContextHolderStrategy;
    }

    public void checkQuota(String location, long maxCalls, long expireSeconds) throws QuotaException {
        Object principal = securityContextHolderStrategy.getContext().getAuthentication().getName();
        RedisKey principalRedisKey = new RedisKeyBuilder().key(application).type(KeyType.QUOTA).category(location).data(principal.toString()).build();
        BoundValueOperations<RedisKey, Long> boundValueOperations = redisTemplate.boundValueOps(principalRedisKey);
        Long callHistory = boundValueOperations.get();
        if (callHistory != null) {
            if (callHistory > maxCalls) {
                throw new QuotaException(40488, "quota.exception", new Object[]{maxCalls, expireSeconds});
            } else {
                // no abuse -> increment
                boundValueOperations.increment(1L);
            }
        } else {
            // if not present we make one
            boundValueOperations.set(1L, expireSeconds, TimeUnit.SECONDS);
        }
    }

}